﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Storage;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace Tile_Engine
{
    public static class TileMap
    {
        #region Declarations

        public const int TileWidth = 48;
        public const int TileHeight = 48;
        public const int MapWidth = 100;
        public const int MapHeight = 100;
        public const int MapLayers = 3;
        private const int defaultTile = 0;
        private static float mapDarkness;

        static private MapSquare[,] mapCells = new MapSquare[MapWidth, MapHeight];

        public static bool EditorMode = false;

        public static SpriteFont spriteFont;
        static private Texture2D tileSheet;

        static private Random random = new Random();

        public const int FloorTileStart = 8;
        public const int FloorTileEnd = 31;
        public const int WallTileStart = 3;
        public const int WallTileEnd = 7;

        #endregion

        #region Initialization
        static public void Initialize(Texture2D tileTexture)
        {
            tileSheet = tileTexture;

            for (int x = 0; x < MapWidth; x++)
            {
                for (int y = 0; y < MapHeight; y++)
                {
                    for (int z = 0; z < MapLayers; z++)
                    {
                        mapCells[x, y] = new MapSquare(defaultTile, 0, 0, "", true);
                    }
                }
            }
        }
        #endregion

        #region Tile and Tile Sheet Handling
        public static int TilesPerRow
        {
            get { return tileSheet.Width / TileWidth; }
        }

        public static Rectangle TileSourceRectangle(int tileIndex)
        {
            return new Rectangle(
                (tileIndex % TilesPerRow) * TileWidth,
            (tileIndex / TilesPerRow) * TileHeight,
            TileWidth,
            TileHeight);
        }
        #endregion

        #region information about Map Cells
        static public int GetCellByPixelX(int pixelX)
        {
            return pixelX / TileWidth;
        }
        static public int GetCellByPixelY(int pixelY)
        {
            return pixelY / TileHeight;
        }
        static public Vector2 GetCellByPixel(Vector2 pixelLocation)
        {
            return new Vector2(GetCellByPixelX((int)pixelLocation.X), GetCellByPixelY((int)pixelLocation.Y));
        }

        static public float Darkness
        {
            get { return mapDarkness; }
            set { mapDarkness = value; }
        }

        static public Vector2 GetCellCentre(int cellX, int cellY)
        {
            return new Vector2(
                (cellX * TileWidth) + (TileWidth / 2),
                (cellY * TileHeight) + (TileHeight / 2));
        }

        static public Vector2 GetCellCentre(Vector2 cell)
        {
            return GetCellCentre(
                (int)cell.X,
                (int)cell.Y);
        }

        static public Rectangle CellWorldRectangle(int cellX, int cellY)
        {
            return new Rectangle(
                cellX * TileWidth,
                cellY * TileHeight,
                TileWidth,
                TileHeight);
        }

        static public Rectangle CellWorldRectangle(Vector2 cell)
        {
            return CellWorldRectangle(
                (int)cell.X,
                (int)cell.Y);
        }

        static public Rectangle CellScreenRectangle(int cellX, int cellY)
        {
            return Camera.WorldToScreen(CellWorldRectangle(cellX, cellY));
        }

        static public Rectangle CellScreenRectangle(Vector2 cell)
        {
            return CellScreenRectangle((int)cell.X, (int)cell.Y);
        }

        static public bool CellIsPassable(int cellX, int cellY)
        {
            MapSquare square = GetMapSquareAtCell(cellX, cellY);
            if (square == null)
                return false;
            else
                return square.Passable;
        }

        static public bool CellIsPassable(Vector2 cell)
        {
            return CellIsPassable((int)cell.X, (int)cell.Y);
        }

        static public bool CellIsPassableByPixel(Vector2 pixelLocation)
        {
            return CellIsPassable(
                GetCellByPixelX((int)pixelLocation.X),
                GetCellByPixelY((int)pixelLocation.Y));
        }

        static public string CellCodeValue(int cellX, int cellY)
        {
            MapSquare square = GetMapSquareAtCell(cellX, cellY);
            if (square == null)
                return"";
            else
                return square.CodeValue;
        }

        static public string CellCodeValue(Vector2 cell)
        {
            return CellCodeValue((int)cell.X, (int)cell.Y);
        }
        #endregion

        #region Information about MapSquare objects

        static public MapSquare GetMapSquareAtCell(int tileX, int tileY)
        {
            if ((tileX >= 0) && (tileX < MapWidth) &&
                (tileY >= 0) && (tileY < MapHeight))
            {
                return mapCells[tileX, tileY];
            }
            else
            {
                return null;
            }
        }

        static public void SetMapSquareAtCell(int tileX, int tileY, MapSquare tile)
        {
            if ((tileX >= 0) && (tileX < MapWidth) &&
                (tileY >= 0) && (tileY < MapHeight))
            {
                mapCells[tileX, tileY] = tile;
            }
        }

        static public void SetTileAtCell(int tileX, int tileY, int layer, int tileIndex)
        {
            if ((tileX >= 0) && (tileX < MapWidth) &&
                (tileY >= 0) && (tileY < MapHeight))
            {
                mapCells[tileX, tileY].LayerTiles[layer] = tileIndex;
            }
        }

        static public MapSquare GetMapSquareAtPixel(int pixelX, int pixelY)
        {
            return GetMapSquareAtCell(GetCellByPixelX(pixelX + (int)Camera.Position.X),
                                      GetCellByPixelY(pixelY + (int)Camera.Position.Y));
        }

        static public MapSquare GetMapSquareAtPixel(Vector2 pixelLocation)
        {
            return GetMapSquareAtPixel(
                (int)pixelLocation.X,
                (int)pixelLocation.Y);
        }
        #endregion

        #region Drawing
        
        static public void Draw(SpriteBatch spriteBatch)
        {
            int startX = GetCellByPixelX((int)Camera.Position.X);
            int endX = GetCellByPixelX((int)Camera.Position.X + Camera.ViewPortWidth);

            int startY = GetCellByPixelY((int)Camera.Position.Y);
            int endY = GetCellByPixelX((int)Camera.Position.Y + Camera.ViewPortHeight);

            for (int x = startX; x <= endX; x++)
                for (int y = startY; y <= endY; y++)
                {
                    for(int z = 0; z < MapLayers; z++)
                    {
                        if ((x >=0) && (y >= 0) &&
                            (x < MapWidth) && (y < MapHeight))
                        {
                            spriteBatch.Draw(
                                tileSheet,
                                CellScreenRectangle(x,y),
                                TileSourceRectangle(mapCells[x,y].LayerTiles[z]),
                                Color.White,
                                0.0f,
                                Vector2.Zero,
                                SpriteEffects.None,
                                1f - ((float)z * 0.1f));
                        }
                    }

                    if (EditorMode)
                    {
                        DrawEditModeItems(spriteBatch, x, y);
                    }
                }
        }
        static public void DrawShadows(SpriteBatch spriteBatch, Vector2 lightAreaVector, double size)
        {

            int startX = GetCellByPixelX((int)Camera.Position.X);
            int endX = GetCellByPixelX((int)Camera.Position.X + Camera.ViewPortWidth);

            int startY = GetCellByPixelY((int)Camera.Position.Y);
            int endY = GetCellByPixelX((int)Camera.Position.Y + Camera.ViewPortHeight);

            MapSquare currSquare;
            Rectangle cellScreenRectangle;

            int sizeModifer = (int)Math.Pow((double)2, size-1);

            for (int x = startX; x <= endX; x++)
                for (int y = startY; y <= endY; y++)
                {
                    for (int z = 0; z < MapLayers; z++)
                    {
                        currSquare = GetMapSquareAtCell(x, y);

                        if ((x >= 0) && (y >= 0) && 
                            (x < MapWidth) && (y < MapHeight) && !currSquare.Passable)
                        {
                            cellScreenRectangle = CellScreenRectangle(x, y);
                            cellScreenRectangle.X += sizeModifer;
                            cellScreenRectangle.Y += sizeModifer;

                            spriteBatch.Draw(
                                tileSheet,
                                cellScreenRectangle,
                                TileSourceRectangle(mapCells[x, y].LayerTiles[z]),
                                Color.Black,
                                0,
                                lightAreaVector,
                                SpriteEffects.None,
                                1.0f);

                        }
                    }

                    //if (EditorMode)
                    //{
                    //    DrawEditModeItems(spriteBatch, x, y);
                    //}
                }
        }

        public static void DrawEditModeItems(SpriteBatch spriteBatch, int x, int y)
        {
            if ((x < 0) || (x >= MapWidth) ||
                (y < 0) || (y >= MapHeight))
                return;
            if (!CellIsPassable(x, y))
            {
                spriteBatch.Draw(
                    tileSheet,
                    CellScreenRectangle(x, y),
                    TileSourceRectangle(1),
                    new Color(255, 0, 0, 80),
                    0.0f,
                    Vector2.Zero,
                    SpriteEffects.None,
                    0.0f);
            }

            if (mapCells[x, y].CodeValue != "")
            {
                Rectangle screenRect = CellScreenRectangle(x, y);

                spriteBatch.DrawString(
                    spriteFont,
                    mapCells[x, y].CodeValue,
                    new Vector2(screenRect.X, screenRect.Y),
                    Color.White,
                    0.0f,
                    Vector2.Zero,
                    1.0f,
                    SpriteEffects.None,
                    0.0f);
            }
        }
        #endregion

        #region Loading and Saving Maps

        public static void SaveMap(FileStream fileStream)
        {
            MapObjectToSerialise mapObjectToSerialise = new MapObjectToSerialise(); //Object to hold all map data that needs to be serialised

            mapObjectToSerialise.mapCells = mapCells;
            mapObjectToSerialise.mapDarkness = Darkness;

            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(fileStream, mapObjectToSerialise);
            fileStream.Close();
        }

        public static void LoadMap(FileStream fileStream)
        {
            MapObjectToSerialise mapObjectToDeserialise = new MapObjectToSerialise();

            try
            {
                BinaryFormatter formatter = new BinaryFormatter();
                mapObjectToDeserialise = (MapObjectToSerialise)formatter.Deserialize(fileStream); 
                //mapCells = (MapSquare[,])formatter.Deserialize(fileStream);
                fileStream.Close();

                mapCells = mapObjectToDeserialise.mapCells;
                Darkness = mapObjectToDeserialise.mapDarkness;
            }
            catch
            {
                ClearMap();
                Darkness = 1f;
            }
        }

        public static void ClearMap()
        {
            for (int x = 0; x < MapWidth; x++)
                for (int y = 0; y < MapHeight; y++)
                    for (int z = 0; z < MapLayers; z++)
                    {
                        mapCells[x, y] = new MapSquare(2, 0, 0, "", true);
                    }
        }
        #endregion

        #region MapGeneration

        public static void GenerateRandomMap()
        {
            int wallChancePerSquare = 10;
            Darkness = (float)random.NextDouble();

            int floorTile = random.Next(FloorTileStart, FloorTileEnd + 1);
            int wallTile = random.Next(WallTileStart, WallTileEnd + 1);

            for (int x = 0; x < MapWidth; x++)
                for (int y = 0; y < MapHeight; y++)
                {
                    mapCells[x, y] = new MapSquare(floorTile, 0, 0, "", true);

                    if ((x == 0) || (y == 0) || (x == MapWidth - 1) || (y == MapHeight - 1))
                    {
                        mapCells[x, y] = new MapSquare(wallTile, 0, 0, "", false);
                        continue;
                    }

                    if ((x == 1) || (y == 1) || (x == MapWidth - 2) || (y == MapHeight - 2))
                    {
                        continue;
                    }

                    if (random.Next(0, 100) <= wallChancePerSquare)
                        mapCells[x, y] = new MapSquare(wallTile, 0, 0, "", false);
                }
       }

       

        #endregion
    }
}
